package core.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import core.config.Factory;

/**
 * オンラインユーティリティクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class ArrayUtil {

	/**
	 * コンストラクタ
	 */
	private ArrayUtil() {
		throw new AssertionError();
	}

	/**
	 * 複製作成処理
	 *
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 複製配列
	 */
	public static <T> T[] copyOf(final T[] arg) {
		return copyOf(arg, null);
	}

	/**
	 * 複製作成処理
	 *
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @param def デフォルト
	 * @return 複製配列
	 */
	public static <T> T[] copyOf(final T[] arg, final T[] def) {
		if (arg != null) {
			return arg.clone();
		}
		return def;
	}

	/**
	 * 初期化
	 * @param <T> ジェネリックス
	 * @param fill 初期化オブジェクト
	 * @param vals 初期化対象配列
	 * @return 初期化済配列
	 */
	public static <T> T[] fill(final T fill, final T[] vals) {
		if (vals != null) {
			for (int i = 0; i < vals.length; i++) {
				vals[i] = fill;
			}
		}
		return vals;
	}

	/**
	 * 複製作成後ソート処理
	 *
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 複製配列
	 */
	public static <T> T[] sort(final T[] arg) {
		if (arg != null) {
			T[] clo = arg.clone();
			Arrays.sort(clo);
			return clo;
		}
		return arg;
	}

	/**
	 * 検索
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @param val 検索対象
	 * @return 存在位置
	 */
	public static <T> int find(final T[] array, final T val) {
		return find(array, val, 0);
	}

	/**
	 * 検索
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @param val 検索対象
	 * @param begin 開始位置
	 * @return 存在位置 存在しない場合、または、valがnullの場合 -1 を返す。
	 */
	public static <T> int find(final T[] array, final T val, final int begin) {
		if (array != null && val != null) {
			for (int i = begin; i < array.length; i++) {
				if (val.equals(array[i])) {
					return i;
				}
			}
		}
		return -1;
	}

	/**
	 * 配列内存在確認
	 * @param <T> ジェネリックス
	 * @param val 値
	 * @param vals 配列
	 * @return 存在した場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean contains(final T val, final T... vals) {
		if (vals != null) {
			for (final T obj : vals) {
				if (obj == val || (obj != null && obj.equals(val))) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 連結処理（null、空文字は連結対象外）
	 * @param <T> ジェネリックス
	 * @param suffix 連結中置子
	 * @param vals 連結対象値
	 * @return 連結結果
	 */
	@SafeVarargs
	public static <T> String concatenate(final String suffix, final T... vals) {
		StringBuilder sb = new StringBuilder();
		if (vals != null) {
			for (final T val : vals) {
				String str = Objects.toString(val, "");
				if (!str.isEmpty()) {
					if (0 < sb.length() && suffix != null) {
						sb.append(suffix);
					}
					sb.append(str);
				}
			}
		}
		return sb.toString();
	}

	/**
	 * 全連結処理
	 * @param <T> ジェネリックス
	 * @param suffix 連結中置子
	 * @param vals 連結対象値
	 * @return 連結結果
	 */
	@SafeVarargs
	public static <T> String concatenateAll(final String suffix, final T... vals) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; vals != null && i < vals.length; i++) {
			if (0 < i && suffix != null) {
				sb.append(suffix);
			}
			String str = Objects.toString(vals[i], "");
			if (!str.isEmpty()) {
				sb.append(str);
			}
		}
		return sb.toString();
	}

	/**
	 * 配列拡張
	 * @param <T> ジェネリックス
	 * @param base 配列
	 * @param vals 拡張する値
	 * @return 拡張配列
	 */
	@SafeVarargs
	public static <T> T[] extend(final T[] base, final T... vals) {
		if (base != null && vals != null) {
			T[] ret = Arrays.copyOf(base, base.length + vals.length);
			System.arraycopy(vals, 0, ret, base.length, vals.length);
			return ret;
		}
		return vals;
	}

	/**
	 * null or 空値判断
	 * @param val 値
	 * @return null or 空値の場合 true を返す。
	 */
	private static boolean isEmptyValue(final Object val) {
		return val == null || (String.class.isInstance(val) && String.class.cast(val).isEmpty());
	}

	/**
	 * 空確認
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 全て、空またはnullの場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean isEmpty(final T... arg) {
		if (arg != null) {
			for (final T val : arg) {
				if (!isEmptyValue(val)) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 空確認
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 空またはnullを含む場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean hasEmpty(final T... arg) {
		if (arg != null) {
			for (final T val : arg) {
				if (isEmptyValue(val)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 先頭値取得
	 * @param <T> ジェネリックス
	 * @param vals 値配列
	 * @return 先頭値
	 */
	@SafeVarargs
	public static <T> T head(final T... vals) {
		if (vals != null && 0 < vals.length) {
			return vals[0];
		}
		return null;
	}

	/**
	 * 末尾値
	 * @param <T> ジェネリックス
	 * @param vals 値配列
	 * @return 末尾値
	 */
	@SafeVarargs
	public static <T> T tail(final T... vals) {
		if (vals != null && 0 < vals.length) {
			return vals[vals.length - 1];
		}
		return null;
	}

	/**
	 * 相違判断
	 * @param <T> ジェネリックス
	 * @param vals 配列
	 * @return 相違がある場合 true　を返す。
	 */
	@SafeVarargs
	public static <T> boolean different(final T... vals) {
		for (int i = 0; vals != null && i < vals.length - 1; i++) {
			if (vals[i] != vals[i + 1]
					&& (vals[i] == null || !vals[i].equals(vals[i + 1]))) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 重複確認（null、空白以外）
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 重複が存在しない場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean isUnique(final T... arg) {
		if (arg != null) {
			Set<T> set = new HashSet<>();
			for (final T val : arg) {
				if (!Objects.toString(val, "").isEmpty() && !set.add(val)) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 値置換
	 * @param <T> ジェネリックス
	 * @param array 設定先配列
	 * @param from 元値
	 * @param to 先値
	 * @return 設定先配列
	 */
	public static <T> T[] replace(final T[] array, final T from, final T to) {
		for (int i = 0; i < array.length; i++) {
			if (from.equals(array[i])) {
				array[i] = to;
			}
		}
		return array;
	}

	/**
	 * 値設定
	 * @param <T> ジェネリックス
	 * @param array 設定先配列
	 * @param val 値
	 * @param index 設定位置保持配列
	 * @return 設定先配列
	 */
	public static <T> T[] setValueTo(final T[] array, final T val, final int... index) {
		for (final int idx : index) {
			if (0 <= idx && idx < array.length) {
				array[idx] = val;
			}
		}
		return array;
	}

	/**
	 * 指定配列内の一致する値を削除した配列を再生成する。
	 * @param <T> ジェネリックス
	 * @param vals 対象配列
	 * @param ele 削除対象
	 * @return 削除後配列
	 */
	@SafeVarargs
	public static <T> T[] eliminate(final T[] vals, final T... ele) {
		if (vals == null || vals.length == 0 || ele == null || ele.length == 0) {
			return vals;
		}

		List<T> e = Arrays.asList(ele);
		List<T> list = new ArrayList<>();
		for (int i = 0; i < vals.length; i++) {
			if (!e.contains(vals[i])) {
				list.add(vals[i]);
			}
		}

		return list.toArray(Factory.<T[]>cast(
					Array.newInstance(vals.getClass().getComponentType(), list.size())));
	}

	/**
	 * 指定配列の指定位置に一致する値を削除した配列を再生成する。
	 * @param <T> ジェネリックス
	 * @param vals 削除対象配列
	 * @param del 削除対象位置配列（昇順）
	 * @return 削除後配列
	 */
	public static <T> T[] remove(final T[] vals, final int... del) {
		if (vals == null || vals.length == 0 || del == null || del.length == 0) {
			return vals;
		}

		List<T> list = new ArrayList<>();
		for (int i = 0, j = 0; i < vals.length; i++) {
			if (j < del.length && i == del[j]) {
				j++;
				continue;
			}
			list.add(vals[i]);
		}

		return list.toArray(Factory.<T[]>cast(
					Array.newInstance(vals.getClass().getComponentType(), list.size())));
	}

	/**
	 * 配列内の空値を取り除いた配列を再作成する。
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @return 配列
	 */
	public static <T> T[] shrink(final T[] array) {
		if (array != null && 0 < array.length) {
			List<T> list = new ArrayList<>();
			for (final T val : array) {
				if (!isEmptyValue(val)) {
					list.add(val);
				}
			}
			return list.toArray(Arrays.copyOf(array, list.size()));
		}
		return array;
	}
}
